home *** CD-ROM | disk | FTP | other *** search
/ Programming Languages Suite / ProgramD2.iso / Borland / Borland Pascal with Objects 7.0 / TVDEMO.ZIP / TVHC.PAS < prev    next >
Pascal/Delphi Source File  |  1992-10-27  |  32KB  |  1,124 lines

  1. {************************************************}
  2. {                                                }
  3. {   Turbo Vision Demo                            }
  4. {   Copyright (c) 1992 by Borland International  }
  5. {                                                }
  6. {************************************************}
  7.  
  8. {===== TVHC version 1.1 ================================================}
  9. {  Turbo Vision help file compiler documentation.                       }
  10. {=======================================================================}
  11. {                                                                       }
  12. {    Refer to DEMOHELP.TXT for an example of a help source file.        }
  13. {                                                                       }
  14. {    This program takes a help script and produces a help file (.HLP)   }
  15. {    and a help context file (.PAS).  The format for the help file is   }
  16. {    very simple.  Each context is given a symbolic name (i.e FileOpen) }
  17. {    which is then put in the context file (i.e. hcFileOpen).  The text }
  18. {    following the topic line is put into the help file.  Since the     }
  19. {    help file can be resized, some of the text will need to be wrapped }
  20. {    to fit into the window.  If a line of text is flush left with      }
  21. {    no preceeding white space, the line will be wrapped.  All adjacent }
  22. {    wrappable lines are wrapped as a paragraph.  If a line begins with }
  23. {    a space it will not be wrapped. For example, the following is a    }
  24. {    help topic for a File|Open menu item.                              }
  25. {                                                                       }
  26. {       |.topic FileOpen                                                }
  27. {       |  File|Open                                                    }
  28. {       |  ---------                                                    }
  29. {       |This menu item will bring up a dialog...                       }
  30. {                                                                       }
  31. {    The "File|Open" will not be wrapped with the "----" line since     }
  32. {    they both begin with a space, but the "This menu..." line will     }
  33. {    be wrapped.                                                        }
  34. {      The syntax for a ".topic" line is:                               }
  35. {                                                                       }
  36. {        .topic symbol[=number][, symbol[=number][...]]                 }
  37. {                                                                       }
  38. {    Note a topic can have multiple symbols that define it so that one  }
  39. {    topic can be used by multiple contexts.  The number is optional    }
  40. {    and will be the value of the hcXXX context in the context file     }
  41. {    Once a number is assigned all following topic symbols will be      }
  42. {    assigned numbers in sequence.  For example,                        }
  43. {                                                                       }
  44. {       .topic FileOpen=3, OpenFile, FFileOpen                          }
  45. {                                                                       }
  46. {    will produce the follwing help context number definitions,         }
  47. {                                                                       }
  48. {        hcFileOpen  = 3;                                               }
  49. {        hcOpenFile  = 4;                                               }
  50. {        hcFFileOpen = 5;                                               }
  51. {                                                                       }
  52. {    Cross references can be imbedded in the text of a help topic which }
  53. {    allows the user to quickly access related topics.  The format for  }
  54. {    a cross reference is as follows,                                   }
  55. {                                                                       }
  56. (*        {text[:alias]}                                               *)
  57. {                                                                       }
  58. {    The text in the brackets is highlighted by the help viewer.  This  }
  59. {    text can be selected by the user and will take the user to the     }
  60. {    topic by the name of the text.  Sometimes the text will not be     }
  61. {    the same as a topic symbol.  In this case you can use the optional }
  62. {    alias syntax.  The symbol you wish to use is placed after the text }
  63. {    after a ':'. The following is a paragraph of text using cross      }
  64. {    references,                                                        }
  65. {                                                                       }
  66. (*      |The {file open dialog:FileOpen} allows you specify which      *)
  67. {       |file you wish to view.  If it also allow you to navigate       }
  68. {       |directories.  To change to a given directory use the           }
  69. (*      |{change directory dialog:ChDir}.                              *)
  70. {                                                                       }
  71. {    The user can tab or use the mouse to select more information about }
  72. {    the "file open dialog" or the "change directory dialog". The help  }
  73. {    compiler handles forward references so a topic need not be defined }
  74. {    before it is referenced.  If a topic is referenced but not         }
  75. {    defined, the compiler will give a warning but will still create a  }
  76. {    useable help file.  If the undefined reference is used, a message  }
  77. {    ("No help available...") will appear in the help window.           }
  78. {=======================================================================}
  79.  
  80. program TVHC;
  81.  
  82. {$S-}
  83.  
  84. {$M 8192,8192,655360}
  85.  
  86. uses Drivers, Objects, Dos, Strings, HelpFile;
  87.  
  88. { If you get a FILE NOT FOUND error when compiling this program
  89.   from a DOS IDE, change to the \BP\EXAMPLES\DOS\TVDEMO directory
  90.   (use File|Change dir).
  91.  
  92.   This will enable the compiler to find all of the units used by
  93.   this program.
  94. }
  95.  
  96. {======================= File Management ===============================}
  97.  
  98. procedure Error(Text: String); forward;
  99.  
  100. type
  101.   PProtectedStream = ^TProtectedStream;
  102.   TProtectedStream = object(TBufStream)
  103.     FileName: FNameStr;
  104.     Mode: Word;
  105.     constructor Init(AFileName: FNameStr; AMode, Size: Word);
  106.     destructor Done; virtual;
  107.     procedure Error(Code, Info: Integer); virtual;
  108.   end;
  109.  
  110. var
  111.   TextStrm,
  112.   SymbStrm: TProtectedStream;
  113.  
  114. const
  115.   HelpStrm: PProtectedStream = nil;
  116.  
  117. constructor TProtectedStream.Init(AFileName: FNameStr; AMode, Size: Word);
  118. begin
  119.   inherited Init(AFileName, AMode, Size);
  120.   FileName := AFileName;
  121.   Mode := AMode;
  122. end;
  123.  
  124. destructor TProtectedStream.Done;
  125. var
  126.   F: File;
  127. begin
  128.   inherited Done;
  129.   if (Mode = stCreate) and ((Status <> stOk) or (ExitCode <> 0)) then
  130.   begin
  131.     Assign(F, FileName);
  132.     Erase(F);
  133.   end;
  134. end;
  135.  
  136. procedure TProtectedStream.Error(Code, Info: Integer);
  137. begin
  138.   case Code of
  139.     stError:
  140.       TVHC.Error('Error encountered in file ' + FileName);
  141.     stInitError:
  142.       if Mode = stCreate then
  143.         TVHC.Error('Could not create ' + FileName)
  144.       else
  145.         TVHC.Error('Could not find ' + FileName);
  146.     stReadError: Status := Code; {EOF is "ok"}
  147.     stWriteError:
  148.       TVHC.Error('Disk full encountered writting file '+ FileName);
  149.   else
  150.       TVHC.Error('Internal error.');
  151.   end;
  152. end;
  153.  
  154. {----- UpStr(Str) ------------------------------------------------------}
  155. {  Returns a string with Str uppercased.                }
  156. {-----------------------------------------------------------------------}
  157.  
  158. function UpStr(Str: String): String;
  159. var
  160.   I: Integer;
  161. begin
  162.   for I := 1 to Length(Str) do
  163.     Str[I] := UpCase(Str[I]);
  164.   UpStr := Str;
  165. end;
  166.  
  167. {----- ReplaceExt(FileName, NExt, Force) -------------------------------}
  168. {  Replace the extension of the given file with the given extension.    }
  169. {  If the an extension already exists Force indicates if it should be   }
  170. {  replaced anyway.                                                     }
  171. {-----------------------------------------------------------------------}
  172.  
  173. function ReplaceExt(FileName: PathStr; NExt: ExtStr; Force: Boolean):
  174.   PathStr;
  175. var
  176.   Dir: DirStr;
  177.   Name: NameStr;
  178.   Ext: ExtStr;
  179. begin
  180.   FileName := UpStr(FileName);
  181.   FSplit(FileName, Dir, Name, Ext);
  182.   if Force or (Ext = '') then
  183.     ReplaceExt := Dir + Name + NExt else
  184.     ReplaceExt := FileName;
  185. end;
  186.  
  187. {----- FExist(FileName) ------------------------------------------------}
  188. {  Returns true if the file exists false otherwise.                     }
  189. {-----------------------------------------------------------------------}
  190.  
  191. function FExists(FileName: PathStr): Boolean;
  192. var
  193.   F: file;
  194.   Attr: Word;
  195. begin
  196.   Assign(F, FileName);
  197.   GetFAttr(F, Attr);
  198.   FExists := DosError = 0;
  199. end;
  200.  
  201.  
  202. {======================== Line Management ==============================}
  203.  
  204. {----- GetLine(S) ------------------------------------------------------}
  205. {  Return the next line out of the stream.                              }
  206. {-----------------------------------------------------------------------}
  207.  
  208. const
  209.   Line: String = '';
  210.   LineInBuffer: Boolean = False;
  211.   Count: Integer = 0;
  212.  
  213. function GetLine(var S: TStream): String;
  214. var
  215.   C, I: Byte;
  216. begin
  217.   if S.Status <> stOk then
  218.   begin
  219.     GetLine := #26;
  220.     Exit;
  221.   end;
  222.   if not LineInBuffer then
  223.   begin
  224.     Line := '';
  225.     C := 0;
  226.     I := 0;
  227.     while (Line[I] <> #13) and (I < 254) and (S.Status = stOk) do
  228.     begin
  229.       Inc(I);
  230.       S.Read(Line[I], 1);
  231.     end;
  232.     Dec(I);
  233.     S.Read(C, 1); { Skip #10 }
  234.     Line[0] := Char(I);
  235.   end;
  236.   Inc(Count);
  237.  
  238.   { Return a blank line if the line is a comment }
  239.   if Line[1] = ';' then Line[0] := #0;
  240.  
  241.   GetLine := Line;
  242.   LineInBuffer := False;
  243. end;
  244.  
  245. {----- UnGetLine(S) ----------------------------------------------------}
  246. {  Return given line into the stream.                                   }
  247. {-----------------------------------------------------------------------}
  248.  
  249. procedure UnGetLine(S: String);
  250. begin
  251.   Line := S;
  252.   LineInBuffer := True;
  253.   Dec(Count);
  254. end;
  255.  
  256. {========================= Error routines ==============================}
  257.  
  258. {----- PrntMsg(Text) ---------------------------------------------------}
  259. {  Used by Error and Warning to print the message.                      }
  260. {-----------------------------------------------------------------------}
  261.  
  262. procedure PrntMsg(Pref: String; var Text: String);
  263. const
  264.   Blank: String[1] = '';
  265. var
  266.   S: String;
  267.   L: array[0..3] of LongInt;
  268. begin
  269.   L[0] := LongInt(@Pref);
  270.   if HelpStrm <> nil then
  271.     L[1] := LongInt(@HelpStrm^.FileName)
  272.   else
  273.     L[1] := LongInt(@Blank);
  274.   L[2] := Count;
  275.   L[3] := LongInt(@Text);
  276.   if Count > 0 then FormatStr(S, '%s: %s(%d): %s'#13#10, L)
  277.   else FormatStr(S, '%s: %s %3#%s', L);
  278.   PrintStr(S);
  279. end;
  280.  
  281. {----- Error(Text) -----------------------------------------------------}
  282. {  Used to indicate an error.  Terminates the program                   }
  283. {-----------------------------------------------------------------------}
  284.  
  285. procedure Error(Text: String);
  286. begin
  287.   PrntMsg('Error', Text);
  288.   Halt(1);
  289. end;
  290.  
  291. {----- Warning(Text) ---------------------------------------------------}
  292. {  Used to indicate an warning.                                         }
  293. {-----------------------------------------------------------------------}
  294.  
  295. procedure Warning(Text: String);
  296. begin
  297.   PrntMsg('Warning', Text);
  298. end;
  299.  
  300. {================ Built-in help context number managment ===============}
  301.  
  302. type
  303.   TBuiltInContext = record
  304.     Text: PChar;
  305.     Number: Word;
  306.   end;
  307.  
  308. { A list of all the help contexts defined in APP }
  309. const
  310.   BuiltInContextTable: array[0..21] of TBuiltInContext = (
  311.     (Text: 'Cascade';   Number: $FF21),
  312.     (Text: 'ChangeDir'; Number: $FF06),
  313.     (Text: 'Clear';     Number: $FF14),
  314.     (Text: 'Close';     Number: $FF27),
  315.     (Text: 'CloseAll';  Number: $FF22),
  316.     (Text: 'Copy';      Number: $FF12),
  317.     (Text: 'Cut';       Number: $FF11),
  318.     (Text: 'DosShell';  Number: $FF07),
  319.     (Text: 'Dragging';  Number: 1),
  320.     (Text: 'Exit';      Number: $FF08),
  321.     (Text: 'New';       Number: $FF01),
  322.     (Text: 'Next';      Number: $FF25),
  323.     (Text: 'Open';      Number: $FF02),
  324.     (Text: 'Paste';     Number: $FF13),
  325.     (Text: 'Prev';      Number: $FF26),
  326.     (Text: 'Resize';    Number: $FF23),
  327.     (Text: 'Save';      Number: $FF03),
  328.     (Text: 'SaveAll';   Number: $FF05),
  329.     (Text: 'SaveAs';    Number: $FF04),
  330.     (Text: 'Tile';      Number: $FF20),
  331.     (Text: 'Undo';      Number: $FF10),
  332.     (Text: 'Zoom';      Number: $FF24)
  333.     );
  334.  
  335. function IsBuiltInContext(Text: String; var Number: Word): Boolean;
  336. var
  337.   Hi, Lo, Mid, Cmp: Integer;
  338. begin
  339.   { Convert Text into a #0 terminted PChar }
  340.   Inc(Text[0]);
  341.   Text[Length(Text)] := #0;
  342.  
  343.   Hi := High(BuiltInContextTable);
  344.   Lo := Low(BuiltInContextTable);
  345.   while Lo <= Hi do
  346.   begin
  347.     Mid := (Hi + Lo) div 2;
  348.     Cmp := StrComp(@Text[1], BuiltInContextTable[Mid].Text);
  349.     if Cmp > 0 then
  350.       Lo := Mid + 1
  351.     else if Cmp < 0 then
  352.       Hi := Mid - 1
  353.     else
  354.     begin
  355.       Number := BuiltInContextTable[Mid].Number;
  356.       IsBuiltInContext := True;
  357.       Exit;
  358.     end;
  359.   end;
  360.   IsBuiltInContext := False;
  361. end;
  362.  
  363. {====================== Topic Reference Management =====================}
  364.  
  365. type
  366.   PFixUp = ^TFixUp;
  367.   TFixUp = record
  368.     Pos: LongInt;
  369.     Next: PFixUp;
  370.   end;
  371.  
  372.   PReference = ^TReference;
  373.   TReference = record
  374.     Topic: PString;
  375.     case Resolved: Boolean of
  376.       True:  (Value: Word);
  377.       False: (FixUpList: PFixUp);
  378.   end;
  379.  
  380.   PRefTable = ^TRefTable;
  381.   TRefTable = object(TSortedCollection)
  382.     function Compare(Key1, Key2: Pointer): Integer; virtual;
  383.     procedure FreeItem(Item: Pointer); virtual;
  384.     function GetReference(var Topic: String): PReference;
  385.     function KeyOf(Item: Pointer): Pointer; virtual;
  386.   end;
  387.  
  388. const
  389.   RefTable: PRefTable = nil;
  390.  
  391. procedure DisposeFixUps(P: PFixUp);
  392. var
  393.   Q: PFixUp;
  394. begin
  395.   while P <> nil do
  396.   begin
  397.     Q := P^.Next;
  398.     Dispose(P);
  399.     P := Q;
  400.   end;
  401. end;
  402.  
  403. {----- TRefTable -------------------------------------------------------}
  404. {  TRefTable is a collection of PReference's used as a symbol table.    }
  405. {  If the topic has not been seen, a forward reference is inserted and  }
  406. {  a fix-up list is started.  When the topic is seen all forward        }
  407. {  references are resolved.  If the topic has been seen already the     }
  408. {  value it has is used.                                                }
  409. {-----------------------------------------------------------------------}
  410.  
  411. function TRefTable.Compare(Key1, Key2: Pointer): Integer;
  412. var
  413.   K1,K2: String;
  414. begin
  415.   K1 := UpStr(PString(Key1)^);
  416.   K2 := UpStr(PString(Key2)^);
  417.   if K1 > K2 then Compare := 1
  418.   else if K1 < K2 then Compare := -1
  419.   else Compare := 0;
  420. end;
  421.  
  422. procedure TRefTable.FreeItem(Item: Pointer);
  423. var
  424.   Ref: PReference absolute Item;
  425.   P, Q: PFixUp;
  426. begin
  427.   if not Ref^.Resolved then DisposeFixUps(Ref^.FixUpList);
  428.   DisposeStr(Ref^.Topic);
  429.   Dispose(Ref);
  430. end;
  431.  
  432. function TRefTable.GetReference(var Topic: String): PReference;
  433. var
  434.   Ref: PReference;
  435.   I: Integer;
  436. begin
  437.   if Search(@Topic, I) then
  438.     Ref := At(I)
  439.   else
  440.   begin
  441.     New(Ref);
  442.     Ref^.Topic := NewStr(Topic);
  443.     Ref^.Resolved := False;
  444.     Ref^.FixUpList := nil;
  445.     Insert(Ref);
  446.   end;
  447.   GetReference := Ref;
  448. end;
  449.  
  450. function TRefTable.KeyOf(Item: Pointer): Pointer;
  451. begin
  452.   KeyOf := PReference(Item)^.Topic;
  453. end;
  454.  
  455. {----- InitRefTable ----------------------------------------------------}
  456. {  Make sure the reference table is initialized.                        }
  457. {-----------------------------------------------------------------------}
  458.  
  459. procedure InitRefTable;
  460. begin
  461.   if RefTable = nil then
  462.     RefTable := New(PRefTable, Init(5,5));
  463. end;
  464.  
  465. {----- RecordReference -------------------------------------------------}
  466. {  Record a reference to a topic to the given stream.  This routine     }
  467. {  handles forward references.                                          }
  468. {-----------------------------------------------------------------------}
  469.  
  470. procedure RecordReference(var Topic: String; var S: TStream);
  471. var
  472.   I: Integer;
  473.   Ref: PReference;
  474.   FixUp: PFixUp;
  475. begin
  476.   InitRefTable;
  477.   Ref := RefTable^.GetReference(Topic);
  478.   if Ref^.Resolved then
  479.     S.Write(Ref^.Value, SizeOf(Ref^.Value))
  480.   else
  481.   begin
  482.     New(FixUp);
  483.     FixUp^.Pos := S.GetPos;
  484.     I := -1;
  485.     S.Write(I, SizeOf(I));
  486.     FixUp^.Next := Ref^.FixUpList;
  487.     Ref^.FixUpList := FixUp;
  488.   end;
  489. end;
  490.  
  491. {----- ResolveReference ------------------------------------------------}
  492. {  Resolve a reference to a topic to the given stream.  This routine    }
  493. {  handles forward references.                                          }
  494. {-----------------------------------------------------------------------}
  495.  
  496. procedure ResolveReference(var Topic: String; Value: Word; var S: TStream);
  497. var
  498.   I: Integer;
  499.   Ref: PReference;
  500.  
  501. procedure DoFixUps(P: PFixUp);
  502. var
  503.   Pos: LongInt;
  504. begin
  505.   Pos := S.GetPos;
  506.   while P <> nil do
  507.   begin
  508.     S.Seek(P^.Pos);
  509.     S.Write(Value, SizeOf(Value));
  510.     P := P^.Next;
  511.   end;
  512.   S.Seek(Pos);
  513. end;
  514.  
  515. begin
  516.   InitRefTable;
  517.   Ref := RefTable^.GetReference(Topic);
  518.   if Ref^.Resolved then
  519.     Error('Redefinition of ' + Ref^.Topic^)
  520.   else
  521.   begin
  522.     DoFixUps(Ref^.FixUpList);
  523.     DisposeFixUps(Ref^.FixUpList);
  524.     Ref^.Resolved := True;
  525.     Ref^.Value := Value;
  526.   end;
  527. end;
  528.  
  529. {======================== Help file parser =============================}
  530.  
  531. {----- GetWord ---------------------------------------------------------}
  532. {   Extract the next word from the given line at offset I.              }
  533. {-----------------------------------------------------------------------}
  534.  
  535. function GetWord(var Line: String; var I: Integer): String;
  536. var
  537.   J: Integer;
  538. const
  539.   WordChars = ['A'..'Z','a'..'z','0'..'9','_'];
  540.  
  541. procedure SkipWhite;
  542. begin
  543.   while (I <= Length(Line)) and (Line[I] = ' ') or (Line[I] = #8) do
  544.     Inc(I);
  545. end;
  546.  
  547. procedure SkipToNonWord;
  548. begin
  549.   while (I <= Length(Line)) and (Line[I] in WordChars) do Inc(I);
  550. end;
  551.  
  552. begin
  553.   SkipWhite;
  554.   J := I;
  555.   if J > Length(Line) then GetWord := ''
  556.   else
  557.   begin
  558.     Inc(I);
  559.     if Line[J] in WordChars then SkipToNonWord;
  560.     GetWord := Copy(Line, J, I - J);
  561.   end;
  562. end;
  563.  
  564. {----- TopicDefinition -------------------------------------------------}
  565. {  Extracts the next topic definition from the given line at I.         }
  566. {-----------------------------------------------------------------------}
  567.  
  568. type
  569.   PTopicDefinition = ^TTopicDefinition;
  570.   TTopicDefinition = object(TObject)
  571.     Topic: PString;
  572.     Value: Word;
  573.     Next: PTopicDefinition;
  574.     constructor Init(var ATopic: String; AValue: Word);
  575.     destructor Done; virtual;
  576.   end;
  577.  
  578. constructor TTopicDefinition.Init(var ATopic: String; AValue: Word);
  579. begin
  580.   Topic := NewStr(ATopic);
  581.   Value := AValue;
  582.   Next := nil;
  583. end;
  584.  
  585. destructor TTopicDefinition.Done;
  586. begin
  587.   DisposeStr(Topic);
  588.   if Next <> nil then Dispose(Next, Done);
  589. end;
  590.  
  591. function TopicDefinition(var Line: String; var I: Integer): PTopicDefinition;
  592. var
  593.   J,K: Integer;
  594.   TopicDef: PTopicDefinition;
  595.   Value: Word;
  596.   Topic, W: String;
  597.   HelpNumber: Word;
  598. const
  599.   HelpCounter: Word = 2; {1 is hcDragging}
  600. begin
  601.   Topic := GetWord(Line, I);
  602.   if Topic = '' then
  603.   begin
  604.     Error('Expected topic definition');
  605.     TopicDefinition := nil;
  606.   end
  607.   else
  608.   begin
  609.     J := I;
  610.     W := GetWord(Line, J);
  611.     if W = '=' then
  612.     begin
  613.       I := J;
  614.       W := GetWord(Line, I);
  615.       Val(W, J, K);
  616.       if K <> 0 then Error('Expected numeric')
  617.       else
  618.       begin
  619.         HelpCounter := J;
  620.         HelpNumber := J;
  621.       end
  622.     end
  623.     else
  624.       if not IsBuiltInContext(Topic, HelpNumber) then
  625.       begin
  626.         Inc(HelpCounter);
  627.         HelpNumber := HelpCounter;
  628.       end;
  629.     TopicDefinition := New(PTopicDefinition, Init(Topic, HelpNumber));
  630.   end;
  631. end;
  632.  
  633. {----- TopicDefinitionList----------------------------------------------}
  634. {  Extracts a list of topic definitions from the given line at I.       }
  635. {-----------------------------------------------------------------------}
  636.  
  637. function TopicDefinitionList(var Line: String; var I: Integer):
  638.   PTopicDefinition;
  639. var
  640.   J: Integer;
  641.   W: String;
  642.   TopicList, P: PTopicDefinition;
  643. begin
  644.   J := I;
  645.   TopicList := nil;
  646.   repeat
  647.     I := J;
  648.     P := TopicDefinition(Line, I);
  649.     if P = nil then
  650.     begin
  651.       if TopicList <> nil then Dispose(TopicList, Done);
  652.       TopicDefinitionList := nil;
  653.       Exit;
  654.     end;
  655.     P^.Next := TopicList;
  656.     TopicList := P;
  657.     J := I;
  658.     W := GetWord(Line, J);
  659.   until W <> ',';
  660.   TopicDefinitionList := TopicList;
  661. end;
  662.  
  663. {----- TopicHeader -----------------------------------------------------}
  664. {  Parse a the Topic header                                             }
  665. {-----------------------------------------------------------------------}
  666.  
  667. const
  668.   CommandChar = '.';
  669.  
  670. function TopicHeader(var Line: String): PTopicDefinition;
  671. var
  672.   I,J: Integer;
  673.   W: String;
  674.   TopicDef: PTopicDefinition;
  675.  
  676. begin
  677.   I := 1;
  678.   W := GetWord(Line, I);
  679.   if W <> CommandChar then
  680.   begin
  681.     TopicHeader := nil;
  682.     Exit;
  683.   end;
  684.   W := UpStr(GetWord(Line, I));
  685.   if W = 'TOPIC' then
  686.     TopicHeader := TopicDefinitionList(Line, I)
  687.   else
  688.   begin
  689.     Error('TOPIC expected');
  690.     TopicHeader := nil;
  691.   end;
  692. end;
  693.  
  694. {----- ReadParagraph ---------------------------------------------------}
  695. { Read a paragraph of the screen.  Returns the paragraph or nil if the  }
  696. { paragraph was not found in the given stream.  Searches for cross      }
  697. { references and updates the XRefs variable.                            }
  698. {-----------------------------------------------------------------------}
  699. type
  700.   PCrossRefNode = ^TCrossRefNode;
  701.   TCrossRefNode = record
  702.     Topic: PString;
  703.     Offset: Integer;
  704.     Length: Byte;
  705.     Next: PCrossRefNode;
  706.   end;
  707. const
  708.   BufferSize = 4096;
  709. var
  710.   Buffer: array[0..BufferSize-1] of Byte;
  711.   Ofs: Integer;
  712.  
  713. function ReadParagraph(var TextFile: TStream; var XRefs: PCrossRefNode;
  714.  var Offset: Integer): PParagraph;
  715. var
  716.   Line: String;
  717.   State: (Undefined, Wrapping, NotWrapping);
  718.   P: PParagraph;
  719.  
  720. procedure CopyToBuffer(var Line: String; Wrapping: Boolean); assembler;
  721. asm
  722.         PUSH    DS
  723.         CLD
  724.         PUSH    DS
  725.         POP     ES
  726.         MOV     DI,OFFSET Buffer
  727.         ADD     DI,Ofs
  728.         LDS     SI,Line
  729.         LODSB
  730.         XOR     AH,AH
  731.         ADD     ES:Ofs,AX
  732.         XCHG    AX,CX
  733.         REP     MOVSB
  734.         XOR     AL,AL
  735.         TEST    Wrapping,1      { Only add a #13, line terminator, if not }
  736.         JE      @@1             { currently wrapping the text. Otherwise  }
  737.         MOV     AL,' '-13       { add a ' '.                              }
  738. @@1:    ADD     AL,13
  739. @@2:    STOSB
  740.         POP     DS
  741.         INC     Ofs
  742. end;
  743.  
  744. procedure AddToBuffer(var Line: String; Wrapping: Boolean);
  745. begin
  746.   if Length(Line) + Ofs > BufferSize - 1 then
  747.     Error('Topic too large.')
  748.   else
  749.     CopyToBuffer(Line, Wrapping);
  750. end;
  751.  
  752. procedure ScanForCrossRefs(var Line: String);
  753. var
  754.   I, BegPos, EndPos, Alias: Integer;
  755. const
  756.   BegXRef = '{';
  757.   EndXRef = '}';
  758.   AliasCh = ':';
  759.  
  760. procedure AddXRef(XRef: String; Offset: Integer; Length: Byte);
  761. var
  762.   P: PCrossRefNode;
  763.   PP: ^PCrossRefNode;
  764. begin
  765.   New(P);
  766.   P^.Topic := NewStr(XRef);
  767.   P^.Offset := Offset;
  768.   P^.Length := Length;
  769.   P^.Next := nil;
  770.   PP := @XRefs;
  771.   while PP^ <> nil do
  772.     PP := @PP^^.Next;
  773.   PP^ := P;
  774. end;
  775.  
  776. procedure ReplaceSpacesWithFF(var Line: String; Start: Integer;
  777.   Length: Byte);
  778. var
  779.   I: Integer;
  780. begin
  781.   for I := Start to Start + Length do
  782.     if Line[I] = ' ' then Line[I] := #$FF;
  783. end;
  784.  
  785. begin
  786.   I := 1;
  787.   repeat
  788.     BegPos := Pos(BegXRef, Copy(Line, I, 255));
  789.     if BegPos = 0 then I := 0
  790.     else
  791.     begin
  792.       Inc(I, BegPos);
  793.       if Line[I] = BegXRef then
  794.       begin
  795.         Delete(Line, I, 1);
  796.         Inc(I);
  797.       end
  798.       else
  799.       begin
  800.         EndPos := Pos(EndXRef, Copy(Line, I, 255));
  801.         if EndPos = 0 then
  802.         begin
  803.           Error('Unterminated topic reference.');
  804.           Inc(I);
  805.         end
  806.         else
  807.         begin
  808.           Alias := Pos(AliasCh, Copy(Line, I, 255));
  809.           if (Alias = 0) or (Alias > EndPos) then
  810.             AddXRef(Copy(Line, I, EndPos - 1), Offset + Ofs + I - 1, EndPos - 1)
  811.           else
  812.           begin
  813.             AddXRef(Copy(Line, I + Alias, EndPos - Alias - 1),
  814.               Offset + Ofs + I - 1, Alias - 1);
  815.             Delete(Line, I + Alias - 1, EndPos - Alias);
  816.             EndPos := Alias;
  817.           end;
  818.           ReplaceSpacesWithFF(Line, I, EndPos-1);
  819.           Delete(Line, I + EndPos - 1, 1);
  820.           Delete(Line, I - 1, 1);
  821.           Inc(I, EndPos - 2);
  822.         end;
  823.       end;
  824.     end;
  825.   until I = 0;
  826. end;
  827.  
  828. function IsEndParagraph: Boolean;
  829. begin
  830.   IsEndParagraph :=
  831.      (Line = '') or
  832.      (Line[1] = CommandChar) or
  833.      (Line = #26) or
  834.      ((Line[1] =  ' ') and (State = Wrapping)) or
  835.      ((Line[1] <> ' ') and (State = NotWrapping));
  836. end;
  837.  
  838. begin
  839.   Ofs := 0;
  840.   ReadParagraph := nil;
  841.   State := Undefined;
  842.   Line := GetLine(TextFile);
  843.   while Line = '' do
  844.   begin
  845.     AddToBuffer(Line, State = Wrapping);
  846.     Line := GetLine(TextFile);
  847.   end;
  848.  
  849.   if IsEndParagraph then
  850.   begin
  851.     ReadParagraph := nil;
  852.     UnGetLine(Line);
  853.     Exit;
  854.   end;
  855.   while not IsEndParagraph do
  856.   begin
  857.     if State = Undefined then
  858.       if Line[1] = ' ' then State := NotWrapping
  859.       else State := Wrapping;
  860.     ScanForCrossRefs(Line);
  861.     AddToBuffer(Line, State = Wrapping);
  862.     Line := GetLine(TextFile);
  863.   end;
  864.   UnGetLine(Line);
  865.   GetMem(P, SizeOf(P^) + Ofs);
  866.   P^.Size := Ofs;
  867.   P^.Wrap := State = Wrapping;
  868.   Move(Buffer, P^.Text, Ofs);
  869.   Inc(Offset, Ofs);
  870.   ReadParagraph := P;
  871. end;
  872.  
  873. {----- ReadTopic -------------------------------------------------------}
  874. { Read a topic from the source file and write it to the help file       }
  875. {-----------------------------------------------------------------------}
  876. var
  877.   XRefs: PCrossRefNode;
  878.  
  879. procedure HandleCrossRefs(var S: TStream; XRefValue: Integer); far;
  880. var
  881.   P: PCrossRefNode;
  882. begin
  883.   P := XRefs;
  884.   while XRefValue > 1 do
  885.   begin
  886.     if P <> nil then P := P^.Next;
  887.     Dec(XRefValue);
  888.   end;
  889.   if P <> nil then RecordReference(P^.Topic^, S);
  890. end;
  891.  
  892. procedure ReadTopic(var TextFile: TStream; var HelpFile: THelpFile);
  893. var
  894.   Line: String;
  895.   P: PParagraph;
  896.   Topic: PHelpTopic;
  897.   TopicDef: PTopicDefinition;
  898.   I, J, Offset: Integer;
  899.   Ref: TCrossRef;
  900.   RefNode: PCrossRefNode;
  901.  
  902. procedure SkipBlankLines(var S: TStream);
  903. var
  904.   Line: String;
  905. begin
  906.   Line := '';
  907.   while Line = '' do
  908.     Line := GetLine(S);
  909.   UnGetLine(Line);
  910. end;
  911.  
  912. function XRefCount: Integer;
  913. var
  914.   I: Integer;
  915.   P: PCrossRefNode;
  916. begin
  917.   I := 0;
  918.   P := XRefs;
  919.   while P <> nil do
  920.   begin
  921.     Inc(I);
  922.     P := P^.Next;
  923.   end;
  924.   XRefCount := I;
  925. end;
  926.  
  927. procedure DisposeXRefs(P: PCrossRefNode);
  928. var
  929.   Q: PCrossRefNode;
  930. begin
  931.   while P <> nil do
  932.   begin
  933.     Q := P;
  934.     P := P^.Next;
  935.     if Q^.Topic <> nil then DisposeStr(Q^.Topic);
  936.     Dispose(Q);
  937.   end;
  938. end;
  939.  
  940. procedure RecordTopicDefinitions(P: PTopicDefinition);
  941. begin
  942.   while P <> nil do
  943.   begin
  944.     ResolveReference(P^.Topic^, P^.Value, HelpFile.Stream^);
  945.     HelpFile.RecordPositionInIndex(P^.Value);
  946.     P := P^.Next;
  947.   end;
  948. end;
  949.  
  950. begin
  951.   { Get Screen command }
  952.   SkipBlankLines(TextFile);
  953.   Line := GetLine(TextFile);
  954.  
  955.   TopicDef := TopicHeader(Line);
  956.  
  957.   Topic := New(PHelpTopic, Init);
  958.  
  959.   { Read paragraphs }
  960.   XRefs := nil;
  961.   Offset := 0;
  962.   P := ReadParagraph(TextFile, XRefs, Offset);
  963.   while P <> nil do
  964.   begin
  965.     Topic^.AddParagraph(P);
  966.     P := ReadParagraph(TextFile, XRefs, Offset);
  967.   end;
  968.  
  969.   I := XRefCount;
  970.   Topic^.SetNumCrossRefs(I);
  971.   RefNode := XRefs;
  972.   for J := 1 to I do
  973.   begin
  974.     Ref.Offset := RefNode^.Offset;
  975.     Ref.Length := RefNode^.Length;
  976.     Ref.Ref := J;
  977.     Topic^.SetCrossRef(J, Ref);
  978.     RefNode := RefNode^.Next;
  979.   end;
  980.  
  981.   RecordTopicDefinitions(TopicDef);
  982.  
  983.   CrossRefHandler := HandleCrossRefs;
  984.   HelpFile.PutTopic(Topic);
  985.  
  986.   if Topic <> nil then Dispose(Topic, Done);
  987.   if TopicDef <> nil then Dispose(TopicDef, Done);
  988.   DisposeXRefs(XRefs);
  989.  
  990.   SkipBlankLines(TextFile);
  991. end;
  992.  
  993. {----- WriteSymbFile ---------------------------------------------------}
  994. { Write the .PAS file containing all screen titles as constants.        }
  995. {-----------------------------------------------------------------------}
  996.  
  997. procedure WriteSymbFile(var SymbFile: TProtectedStream);
  998. const
  999.   HeaderText1 =
  1000.     'unit ';
  1001.   HeaderText2 =
  1002.     ';'#13#10 +
  1003.     #13#10 +
  1004.     'interface'#13#10 +
  1005.     #13#10 +
  1006.     'const'#13#10 +
  1007.     #13#10;
  1008.   FooterText =
  1009.     #13#10 +
  1010.     'implementation'#13#10 +
  1011.     #13#10 +
  1012.     'end.'#13#10;
  1013.   Header1: array[1..Length(HeaderText1)] of Char = HeaderText1;
  1014.   Header2: array[1..Length(HeaderText2)] of Char = HeaderText2;
  1015.   Footer: array[1..Length(FooterText)] of Char = FooterText;
  1016. var
  1017.   I, Count: Integer;
  1018.   Dir: DirStr;
  1019.   Name: NameStr;
  1020.   Ext: ExtStr;
  1021.  
  1022. procedure DoWriteSymbol(P: PReference); far;
  1023. var
  1024.   L: array[0..1] of LongInt;
  1025.   Line: String;
  1026.   I: Word;
  1027. begin
  1028.   if (P^.Resolved) then
  1029.   begin
  1030.     if not IsBuiltInContext(P^.Topic^, I) then
  1031.     begin
  1032.       L[0] := LongInt(P^.Topic);
  1033.       L[1] := P^.Value;
  1034.       FormatStr(Line, '  hc%-20s = %d;'#13#10, L);
  1035.       SymbFile.Write(Line[1], Length(Line));
  1036.     end
  1037.   end
  1038.   else Warning('Unresolved forward reference "' + P^.Topic^ + '"');
  1039. end;
  1040.  
  1041. begin
  1042.   SymbFile.Write(Header1, SizeOf(Header1));
  1043.   FSplit(SymbFile.FileName, Dir, Name, Ext);
  1044.   SymbFile.Write(Name[1], Length(Name));
  1045.   SymbFile.Write(Header2, SizeOf(Header2));
  1046.  
  1047.   RefTable^.ForEach(@DoWriteSymbol);
  1048.  
  1049.   SymbFile.Write(Footer, SizeOf(Footer));
  1050. end;
  1051.  
  1052. {----- ProcessText -----------------------------------------------------}
  1053. { Compile the given stream, and output a help file.                     }
  1054. {-----------------------------------------------------------------------}
  1055.  
  1056. procedure ProcessText(var TextFile, HelpFile, SymbFile: TProtectedStream);
  1057. var
  1058.   HelpRez: THelpFile;
  1059. begin
  1060.   HelpRez.Init(@HelpFile);
  1061.   while TextFile.Status = stOk do
  1062.     ReadTopic(TextFile, HelpRez);
  1063.   WriteSymbFile(SymbFile);
  1064.   HelpRez.Done;
  1065. end;
  1066.  
  1067. {========================== Program Block ==========================}
  1068.  
  1069. var
  1070.   TextName,
  1071.   HelpName,
  1072.   SymbName: PathStr;
  1073.  
  1074. procedure ExitClean; far;
  1075. begin
  1076.   { Print a message if an out of memory error encountered }
  1077.   if ExitCode = 201 then
  1078.   begin
  1079.     Writeln('Error: Out of memory.');
  1080.     ErrorAddr := nil;
  1081.     ExitCode := 1;
  1082.   end;
  1083.  
  1084.   { Clean up files }
  1085.   TextStrm.Done;
  1086.   SymbStrm.Done;
  1087. end;
  1088.  
  1089. begin
  1090.   { Banner messages }
  1091.   PrintStr('Help Compiler  Version 1.1  Copyright (c) 1992 Borland International.'#13#10);
  1092.   if ParamCount < 1 then
  1093.   begin
  1094.     PrintStr(
  1095.       #13#10 +
  1096.       '  Syntax:  TVHC <Help text>[.TXT] [<Help file>[.HLP] [<Symbol file>[.PAS]]'#13#10 +
  1097.       #13#10+
  1098.       '     Help text   = Help file source'#13#10 +
  1099.       '     Help file   = Compiled help file'#13#10 +
  1100.       '     Symbol file = A Pascal file containing all the screen names as CONST''s'#13#10);
  1101.     Halt(0);
  1102.   end;
  1103.  
  1104.   { Calculate file names }
  1105.   TextName := ReplaceExt(ParamStr(1), '.TXT', False);
  1106.   if not FExists(TextName) then
  1107.     Error('File "' + TextName + '" not found.');
  1108.   if ParamCount >= 2 then
  1109.     HelpName := ReplaceExt(ParamStr(2), '.HLP', False) else
  1110.     HelpName := ReplaceExt(TextName, '.HLP',  True);
  1111.   if ParamCount >= 3 then
  1112.     SymbName := ReplaceExt(ParamStr(3), '.PAS', False) else
  1113.     SymbName := ReplaceExt(HelpName, '.PAS', True);
  1114.  
  1115.   ExitProc := @ExitClean;
  1116.  
  1117.   RegisterHelpFile;
  1118.  
  1119.   TextStrm.Init(TextName, stOpenRead, 1024);
  1120.   SymbStrm.Init(SymbName, stCreate,   1024);
  1121.   HelpStrm := New(PProtectedStream, Init(HelpName, stCreate, 1024));
  1122.   ProcessText(TextStrm, HelpStrm^, SymbStrm);
  1123. end.
  1124.